/**
 * MooGrid v0.1 - Mootools Grid Control
 * 
 * The MIT License
 *
 * Copyright (c) <2008> <WangXingcao & Zarknight>
 * 
 * jy_wxchao@hotmail.com
 * zarknight@gmail.com
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

var MooGrid = new Class({

    Implements: [Events, Options],
    
    options: {
        url: '',
        pageSize: 20,
        baseParams: {},
        headerHeight: 23,
        cellHeight: 19,
        
        dataInfo: {
            pageNo: 1,
            pageSize: 20, 
            sort: '',
            dir: '',
            totalCount: 0,
            pageCount: 1
        }
    },
    
	/**
	 * On construct
	 * @param {Element/String} container - the Element or id of the container for grid
	 * 
	 * @param {Object} options
	 *   - url {String}: url of the data fetch from
	 *   - pageSize {Integer}: record size per page
	 *   - baseParams {Object}: parameters for every paging request
	 *   - headerHeight {Integer}: height of the column's header
	 *   - cellHeight {Integer}: height of the column's data cell
	 * 
	 * @param {Object} columnModel
	 *   - header {String}: the header text of the column
	 *   - mapping {String}: name of the data for the column in the record 
	 *   - sortable {Boolean}: column sortable
	 *   - width {Number/String}: column width, such as 40 or '20%'
	 *   - renderer {Function}: a function that return the customed cell content html
	 *   
	 * On changing data page or sortting column, 4 parameters will be send to the backend:
	 *   - dir
	 *   - pageNo
	 *   - pageSize
	 *   - sort
	 */
    initialize: function(container, options, columnModel){
        if (!this.checkParams(container, options, columnModel)) {
            return;
        }
        
        this.dataInfo = this.options.dataInfo;
        this.dataInfo.pageSize = this.options.pageSize;
        this.baseParams = this.options.baseParams;
        
        this.EL = this.container;
        this.headers = [];
        this.rows = [];
        
        this.createGridTable();
        this.createPageFooter();
        this.createJsonRequest();
        this.loadData();
    },
    
    /* [private] validate the paramertes */
    checkParams: function(container, options, columnModel){
        if ($(container)) {
            this.container = $(container).setStyle('border', '1px solid #ededed');
        }
        else {
            alert('Could not find the container!' + container);
            return false;
        }
        
        if (!options || !options.url) {
            alert('Url required!');
            return false;
        }
        else {
            this.setOptions(options);
        }
        
        if (!columnModel) {
            alert('Column Model not defined!');
            return false;
        }
        else {
            this.columnModel = columnModel;
        }
        
        return true;
    },
    
    /* [private] create the table dom of the grid */
    createGridTable: function(){
        var gridTable = new Element('table', {
            'id': MooGrid.createId(),
            'cellPadding': 0,
            'cellSpacing': 0,
            'border': 0,
            'class': 'moogrid-tabe'
        }).inject(this.EL);
        
        var tbody = new Element('tbody').inject(gridTable);
        gridTable.tbody = tbody;
		
        this.createGridHeader(tbody);
        this.createContentArea(tbody);
        this.gridTable = gridTable;
    },
    
    /* [private] create the header of the grid */
    createGridHeader: function(tbody){
        var trHeader = new Element('tr', {
            'id': MooGrid.createId(),
            'class': 'moogrid-header-tr'
        }).inject(tbody);
        
        var createThHeader = function(column, index){
            var th = new Element('td', {
                'id': MooGrid.createId(),
                'class': 'moogrid-header-th',
                'height': this.options.headerHeight,
                'events': {
                    'mouseover': function(){
                        this.addClass('moogrid-header-th-over');
                        if (this.column.sortable) {
                            this.addClass('moogrid-header-th-over-sortble')
                        }
                    },
                    'mouseout': function(){
                        this.removeClass('moogrid-header-th-over');
                        if (this.column.sortable) {
                            this.removeClass('moogrid-header-th-over-sortble')
                        }
                    },
                    'click': function(){
                        this.grid.sortGrid(this);
                    }
                }
            }).inject(trHeader);
            
			if(column.width){
				th.setStyle('width', column.width);
			}
			
            var div = new Element('div', {
                'text': column.header
            }).inject(th);
            
            var img = new Element('img', {
                'id': MooGrid.createId(),
                'src': MooGrid.sortImg,
                'class': 'moogrid-sort-img'
            }).inject(div);
            
            th.sortImg = img;
            th.column = column;
            th.grid = this;
            
            this.headers.include(th);
        };
        
        this.columnModel.each(createThHeader, this);
        tbody.trHeader = trHeader;
    },
    
    /* [private] create the content area of the grid */
    createContentArea: function(tbody){
        var grid = this;
        for (var i = 0; i < this.options.pageSize; i++) {
            var trRow = new Element('tr', {
                'id': MooGrid.createId(),
                'class': 'moogrid-row-tr-odd',
                'events': {
                    'click': function(){
                        if (!this.recordData) {
                            return;
                        }
                        grid.select(this);
                    }
                }
            }).inject(tbody);
            
            trRow.index = i;
            trRow.contentCells = new Hash();
            this.rows.include(trRow);
            
            for (var j = 0; j < this.columnModel.length; j++) {
                this.createTdCell(trRow, this.columnModel[j], j);
            }
        }
    },
    
    /* [private] create the data cells of the grid */
    createTdCell: function(trRow, column, index){
        var td = new Element('td', {
            'id': MooGrid.createId(),
            'class': 'moogrid-row-td-odd',
            'height': this.options.cellHeight
        }).inject(trRow);
        
        var div = new Element('div', {
            'id': MooGrid.createId(),
            'events': {
                'mouseover': function(){
                    td.addClass('moogrid-row-td-over');
                    trRow.addClass('moogrid-row-tr-over');
                },
                'mouseout': function(){
                    td.removeClass('moogrid-row-td-over');
                    trRow.removeClass('moogrid-row-tr-over');
                }
            }
        }).inject(td);
        
        trRow.contentCells.include(column.mapping, div);
    },
    
	/* [private] create the paging toolbar */
    createPageFooter: function(){
        var grid = this;
        
        var footer = new Element('div', {
            'id': MooGrid.createId(),
            'class': 'moogrid-footer'
        }).inject(this.EL);
        
        var createPageIcon = function(cls, clsover, iconType){
            var div = new Element('div', {
                'id': MooGrid.createId(),
                'class': 'moogrid-footer-icon-div'
            }).inject(footer);
            
            var img = new Element('img', {
                'id': MooGrid.createId(),
                'src': MooGrid.sortImg,
                'class': cls,
                'events': {
                    'mouseover': function(){
                        if (!grid.isPage(iconType)) {
                            this.addClass(clsover);
                        }
                    },
                    'mouseout': function(){
                        this.removeClass(clsover);
                    },
                    'click': function(){
                        grid.goPage(iconType);
                    }
                }
            }).inject(div);
            
            return img;
        };
        
        var createGoPage = function(){
            var div = new Element('div', {
                'id': MooGrid.createId(),
                'class': 'moogrid-div-page-go'
            }).inject(footer);
            
            var inputGo = new Element('input', {
                'id': MooGrid.createId(),
                'styles': {
                    'width': 25,
                    'height': 15,
                    'border': '1px solid #cccccc'
                },
                'events': {
                    'blur': function(){
                        grid.goPageNo(this.value);
                    },
                    'keydown': function(event){
                        if (event.code == Event.Keys.enter) {
                            grid.goPageNo(this.value);
                            this.blur();
                        }
                    }
                }
            }).inject(div);
            
            grid.pageCountInfo = new Element('span', {
                'id': MooGrid.createId()
            }).inject(div);
            
            return inputGo;
        };
        
        this.firstIcon = createPageIcon('moogrid-first-icon', 'moogrid-first-icon-over', 'first');
        this.prevIcon = createPageIcon('moogrid-prev-icon', 'moogrid-prev-icon-over', 'prev');
        footer.grab(this.createSplitDivFloatLeft());
        
        this.goPageNoInput = createGoPage();
        footer.grab(this.createSplitDivFloatLeft());
        
        this.nextIcon = createPageIcon('moogrid-next-icon', 'moogrid-next-icon-over', 'next');
        this.lastIcon = createPageIcon('moogrid-last-icon', 'moogrid-last-icon-over', 'last');
        footer.grab(this.createClearDiv());
    },
    
    createClearDiv: function(){
        return new Element('div', {
            'class': 'clear'
        });
    },
    
    createSplitDivFloatLeft: function(){
        return new Element('div', {
            'class': 'moogrid-split-div-float-left'
        });
    },
    
    createJsonRequest: function(){
        this.JSON_REQUEST = new Request.JSON({
            url: this.options.url,
            onComplete: function(result){
	            if (!this.checkJsonResult(result)) {
	                return;
	            }
	            this.assembleData(result.records);
	            this.changePageIconView();
	            this.clearSelected();
	        }.bind(this)
        });
    },
    
    checkJsonResult: function(result){
        if (!result || !$defined(result.totalCount) || !result.records) {
            alert('invalid return data!');
            return false;
        }
        
        var records = result.records;
        var totalCount = result.totalCount;
        
        var pageSize = this.dataInfo.pageSize;
        var pageCount = 1;
        
        if (totalCount % pageSize == 0) {
            pageCount = totalCount / pageSize;
        }
        else {
            pageCount = (Math.floor(totalCount / pageSize) - 0) + 1;
        }
        
        if (pageCount <= 0) {
            pageCount = 1;
        }
        
        if (this.dataInfo.pageNo > pageCount) {
            alert('data error!');
            this.dataInfo.pageNo = 1;
            this.loadData();
            return false;
        }
        
        if (this.dataInfo.pageNo > 1 && records.length == 0) {
            alert('data error!');
            this.dataInfo.pageNo = 1;
            this.loadData();
            return false;
        }
        
        this.dataInfo.totalCount = totalCount;
        this.dataInfo.pageCount = pageCount;
        return true;
    },
    
    /* [private] assemble data and fill into the cells of the grid */
    assembleData: function(records){
        for (var i = 0; i < this.dataInfo.pageSize; i++) {
            var rowData = records[i];
            this.rows[i].recordData = rowData;
			
            var row = this.rows[i].contentCells;
            var columns = this.columnModel;
			
            for (var j = 0; j < columns.length; j++) {
                var name = columns[j].mapping;
				var renderer = columns[j].renderer;
				
				var content = '';
				if(rowData){
					var ct = rowData[name] || '';
					content = renderer ? renderer(ct, rowData) : ct;
				}
				
                row[name].set('html', content);
            }
        }
    },
    
    changePageIconView: function(){
        var f = this.firstIcon;
        var l = this.lastIcon;
        var p = this.prevIcon;
        var n = this.nextIcon;
        
        if (this.isFirstPage()) {
            f.removeClass('moogrid-first-icon');
            p.removeClass('moogrid-prev-icon');
            f.addClass('moogrid-first-icon-disable');
            p.addClass('moogrid-prev-icon-disable');
        }
        else {
            f.removeClass('moogrid-first-icon-disable');
            p.removeClass('moogrid-prev-icon-disable');
            f.addClass('moogrid-first-icon');
            p.addClass('moogrid-prev-icon');
        }
        
        if (this.isLastPage()) {
            l.removeClass('moogrid-last-icon');
            n.removeClass('moogrid-next-icon');
            l.addClass('moogrid-last-icon-disable');
            n.addClass('moogrid-next-icon-disable');
        }
        else {
            l.removeClass('moogrid-last-icon-disable');
            n.removeClass('moogrid-next-icon-disable');
            l.addClass('moogrid-last-icon');
            n.addClass('moogrid-next-icon');
        }
        
        this.goPageNoInput.set('value', this.dataInfo.pageNo);
        this.pageCountInfo.set('text', '/' + this.dataInfo.pageCount);
    },
	
	sortGrid: function(header){
        if (!header.column.sortable) {
            return;
        }
        this.changeSortImg(header);
        this.dataInfo.sort = header.column.mapping;
        this.dataInfo.dir = this.currentSortImg.currentSort;
        this.loadData();
    },
    
    isFirstPage: function(){
        return this.dataInfo.pageNo == 1;
    },
    
    isLastPage: function(){
        return this.dataInfo.pageNo == this.dataInfo.pageCount;
    },
    
    isPage: function(type){
        if (type == 'first' || type == 'prev') {
            return this.isFirstPage();
        }
        if (type == 'last' || type == 'next') {
            return this.isLastPage();
        }
        return false;
    },
    
    changeSortImg: function(header){
        if (header.column.sortable) {
            var sortImg = header.sortImg;
            if (sortImg.currentSort && sortImg.currentSort == 'asc') {
                sortImg.removeClass('moogrid-sort-img-asc');
                sortImg.addClass('moogrid-sort-img-desc');
                sortImg.currentSort = 'desc';
            }
            else {
                sortImg.removeClass('moogrid-sort-img-desc');
                sortImg.addClass('moogrid-sort-img-asc');
                sortImg.currentSort = 'asc';
            }
            if (this.currentSortImg && this.currentSortImg != sortImg) {
                this.currentSortImg.removeClass('moogrid-sort-img-asc');
                this.currentSortImg.removeClass('moogrid-sort-img-desc');
            }
            this.currentSortImg = sortImg;
        }
    },
    
    select: function(row){
        if (this.currentSelectedRow) {
            this.currentSelectedRow.removeClass('moogrid-row-tr-selected');
        }
        row.addClass('moogrid-row-tr-selected');
        this.currentSelectedRow = row;
    },
    
    clearSelected: function(row){
        if (this.currentSelectedRow) {
            this.currentSelectedRow.removeClass('moogrid-row-tr-selected');
            this.currentSelectedRow = null;
        }
    },
    
    loadData: function(){
        var params = $extend({
            'pageNo': 	this.dataInfo.pageNo,
            'pageSize': this.dataInfo.pageSize,
            'sort': 	this.dataInfo.sort,
            'dir': 		this.dataInfo.dir
        }, this.baseParams);
		
        this.JSON_REQUEST.get(params);
    },
    
    goPage: function(where){
        if (where == 'first') {
            if (this.dataInfo.pageNo == 1) {
                return;
            }
            this.dataInfo.pageNo = 1;
        }
        else {
            if (where == 'last') {
                if (this.dataInfo.pageNo == this.dataInfo.pageCount) {
                    return;
                }
                this.dataInfo.pageNo = this.dataInfo.pageCount;
            }
            else {
                if (where == 'next') {
                    if (this.dataInfo.pageNo >= this.dataInfo.pageCount) {
                        return;
                    }
                    this.dataInfo.pageNo++;
                }
                else {
                    if (where == 'prev') {
                        if (this.dataInfo.pageNo == 1) {
                            return;
                        }
                        this.dataInfo.pageNo--;
                    }
                    else {
                        this.goPageNo(where);
                        return;
                    }
                }
            }
        }
        this.loadData();
    },
    
    goPageNo: function(pageNo){
        var pno;
		
        try {
            pno = pageNo - 0;
        } catch (e) {
            pno = 0;
        }
		
        if (!pno) {
            pno = 0;
        }
		
        if (pno <= 0 || pno > this.dataInfo.pageCount || pno == this.dataInfo.pageNo) {
            this.goPageNoInput.set('value', this.dataInfo.pageNo);
        }
        else {
            this.dataInfo.pageNo = pno;
            this.loadData();
        }
    },
    
	/**
	 * Get the record data of the current selected row
	 */
    getSelected: function(){
        if (this.currentSelectedRow) {
            return this.currentSelectedRow.recordData || {};
        }
		return {};
    },
    
	/**
	 * Add event listener on row's double clicked
	 * @param {Function} fn
	 */
    addRowDblClick: function(fn){
		$$(this.rows).addEvent('dblclick', function(event){
            if (this.recordData) {
				fn(this.index, this.recordData);
			}
        });
    }
});

MooGrid.extend({
	sortImg: '',
	currentIdNo: 0,
	
	createId: function() {
	    MooGrid.currentIdNo++;
	    return 'moogrid-' + MooGrid.currentIdNo;
	}
});